package uk.ac.cam.echo.app;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import uk.ac.cam.echo.ScheduleEvent;
import uk.ac.cam.echo.messages.CheckInMessage;
import uk.ac.cam.echo.messages.FriendCheckInMessage;
import uk.ac.cam.echo.messages.FriendListMessage;
import uk.ac.cam.echo.messages.LocationMessage;
import uk.ac.cam.echo.messages.LocationTableMessage;
import uk.ac.cam.echo.messages.PendingFriendRequestsMessage;
import uk.ac.cam.echo.messages.Message;
import uk.ac.cam.echo.messages.ScheduleMessage;
import uk.ac.cam.echo.messages.SecurityMessage;
import uk.ac.cam.echo.messages.UserMessage;

/**
 * @author Tom Smith
 */
public class Reader implements Runnable {

	private Socket m_Sock;
	private final int TIMEOUT = 30;	// Timeout to wait in seconds.

	private List<UserMessage> m_FriendList = new LinkedList<UserMessage>();
	private List<UserMessage> m_FriendRequests = new LinkedList<UserMessage>();
	private List<ScheduleEvent> m_Schedule = new LinkedList<ScheduleEvent>();
	private List<CheckInMessage> m_FriendCheckIns = new LinkedList<CheckInMessage>();
	private List<LocationMessage> m_LocationList = new LinkedList<LocationMessage>();

	private Message m_RFIDTagMessage;

	public Reader(Socket sock) {
		this.m_Sock = sock;
	}

	@Override
	public void run() {
		try {

			ObjectInputStream objectInputStream = new ObjectInputStream(this.m_Sock.getInputStream());

			while (true) {
				readMessage((Message)objectInputStream.readObject());
			}

		} catch (IOException e) {
			System.err.println("Server has ended the connection.");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Reads and acts on a message from the server.
	 * @param m The message to read.
	 */
	private synchronized void readMessage(Message m) {

		if (m instanceof SecurityMessage) {
			this.m_RFIDTagMessage = m;

			this.notify();

		} else if (m instanceof UserMessage) {
			this.m_RFIDTagMessage = m;

			this.notify();

		} else if (m instanceof LocationMessage) {
			this.m_RFIDTagMessage = m;

			this.notify();

		} else if (m instanceof ScheduleMessage) {
			// Update Schedule we have on record.
			ScheduleMessage s = (ScheduleMessage)m;
			this.m_Schedule = s.getSchedule();
			// Let the thread know that we've received a schedule.
			this.notify();

		} else if (m instanceof FriendListMessage) {
			// Receive friend list.
			FriendListMessage f = (FriendListMessage)m;
			this.m_FriendList = f.getFriendList();
			// Let the thread know that we've checked them all.
			this.notify();

		} else if (m instanceof PendingFriendRequestsMessage) {
			// Receive new friend requests.
			PendingFriendRequestsMessage f = (PendingFriendRequestsMessage)m;
			this.m_FriendRequests = f.getFriendRequests();
			// Let the thread know that we've checked them all.
			this.notify();

		} else if (m instanceof FriendCheckInMessage) {
			FriendCheckInMessage f = (FriendCheckInMessage) m;
			this.m_FriendCheckIns = f.getFriendCheckIns();
			Collections.sort(this.m_FriendCheckIns, Collections.reverseOrder());

			// Let the thread know that we've checked them all.
			this.notify();
		} else if (m instanceof LocationTableMessage) {
			LocationTableMessage l = (LocationTableMessage) m;
			this.m_LocationList = l.getList();

			// Let the thread know that we've checked them all.
			this.notify();
		} else {
			// remove anything we set, because it is no longer valid.
			m_RFIDTagMessage = null;
			m_FriendList = new LinkedList<UserMessage>();
			m_FriendRequests = new LinkedList<UserMessage>();
			m_Schedule = new LinkedList<ScheduleEvent>();
			m_FriendCheckIns = new LinkedList<CheckInMessage>();
			m_LocationList = new LinkedList<LocationMessage>();

			this.notify();
		}
	}

	/**
	 * Gets friend check ins.
	 * @param friendRFID the RFID of the friend to get check ins for.
	 * @return A list of CheckIns of your friends.
	 * @throws InterruptedException
	 */
	public synchronized List<CheckInMessage> getFriendCheckIns(String friendRFID) throws InterruptedException {
		/*
		 *  This will be called by the main thread after sending a message for friend requests.
		 *  We then tell it to wait, until we get the PendingFriendRequestsMessage back.
		 *  By this point, we'll have added the friend requests to the list we return.
		 *  After 30 seconds, the message could have came already, so we'll timeout.
		 */
		this.wait(TIMEOUT * 1000);
		return this.m_FriendCheckIns;
	}

	/**
	 * Gets all of the current friend requests for this user.
	 * @return A list of the RFIDs of users wanting to be friends.
	 * @throws InterruptedException
	 */
	public synchronized List<UserMessage> getFriendRequests() throws InterruptedException {
		/*
		 *  This will be called by the main thread after sending a message for friend requests.
		 *  We then tell it to wait, until we get the PendingFriendRequestsMessage back.
		 *  By this point, we'll have added the friend requests to the list we return.
		 *  After 30 seconds, the message could have came already, so we'll timeout.
		 */
		this.wait(TIMEOUT * 1000);
		return this.m_FriendRequests;
	}

	/**
	 * Gets all of the current friend requests for this user.
	 * @return A list of the RFIDs of users wanting to be friends.
	 * @throws InterruptedException
	 */
	public synchronized List<UserMessage> getFriendList() throws InterruptedException {
		/*
		 *  This will be called by the main thread after sending a message for friend list.
		 *  We then tell it to wait, until we get the FriendListMessage back.
		 *  By this point, we'll have added the friends to the list we return.
		 *  After 30 seconds, the message could have came already, so we'll timeout.
		 */
		this.wait(TIMEOUT * 1000);
		return this.m_FriendList;
	}

	public synchronized List<LocationMessage> getLocations() throws InterruptedException {
		/*
		 *  We wait to get back a Message from the server, after sending an RFIDMessage.
		 */
		this.wait(TIMEOUT * 1000);
		return this.m_LocationList;
	}

	/**
	 * Get a Message relating to the RFID tag scanned.
	 * @return A UserMessage, LocationMessage or SecurityMessage, depending on the type.
	 * @throws InterruptedException
	 */
	public synchronized Message getMessage() throws InterruptedException {
		/*
		 *  We wait to get back a Message from the server, after sending an RFIDMessage.
		 */
		this.wait(TIMEOUT * 1000);
		return this.m_RFIDTagMessage;
	}

	/**
	 * Gets the Schedule for the evening.
	 * @return A list of ScheduleEvents.
	 * @throws InterruptedException
	 */
	public synchronized List<ScheduleEvent> getSchedule() throws InterruptedException {
		/*
		 *  This will be called by the main thread after sending a message for the schedule.
		 *  We then tell it to wait, until we get the ScheduleMessage back.
		 *  By this point, we'll have set the list to the Schedule.
		 *  After 30 seconds, the message could have came already, so we'll timeout.
		 */
		this.wait(TIMEOUT * 1000);
		return this.m_Schedule;
	}

}
